/**
 * @module	Events
 * @author	zfkun<zfkun@msn.com>
 * 
 * @method
 * 		addEvent
 * 		addEvents
 * 		removeEvent
 * 		removeEvents
 * 		fireEvent
 * 		removeOn
 */
ZF.add('events', function(Z) {

	var F = Z.Function,
        A = Z.Array,

        EVENT_CACHE = '__events',

		METHOD_ADD = 'addEvent',
		METHOD_ADDS = 'addEvents',
		METHOD_REMOVE = 'removeEvent',
		METHOD_REMOVES = 'removeEvents',
		METHOD_FIRE = 'fireEvent',
		METHOD_REMOVEON = 'removeOn',
        
        Events = {};

	Events[METHOD_ADD] = function(type, fn, internal, once) {
        var self = this;

		type = self[METHOD_REMOVEON](type);
		
        if (!self[EVENT_CACHE]) {
            self[EVENT_CACHE] = {};
        }
		
        if (fn != Z.emptyFn){
			self[EVENT_CACHE][type] = self[EVENT_CACHE][type] || [];
			
			// fix bug with Prototype js lib's Object.include method
            if (A.indexOf(self[EVENT_CACHE][type], fn) < 0) {
                self[EVENT_CACHE][type].push(fn);
            }

            /*
			if (window.Prototype && Prototype.Version && A.indexOf(this[EVENT_CACHE][type], fn) < 0) this[EVENT_CACHE][type].push(fn);
			else this[EVENT_CACHE][type].include(fn);*/

			if (internal) {
                fn.internal = true;
            }

            if (once) {
                fn.once = true;
            }
		}

		return self;
	};

	Events[METHOD_ADDS] = function(events){
		for (var type in events) {
            this[METHOD_ADD](type, events[type]);
        }
		return this;
	};

	Events[METHOD_FIRE] = function(type, args, delay) {
        var self = this;
		type = self[METHOD_REMOVEON](type);
		
        if (!self[EVENT_CACHE] || !self[EVENT_CACHE][type]) {
            return self;
        }

        var onces = [];
		Z.each(self[EVENT_CACHE][type], function(fn) {
			F.create(fn, { bind : this, delay : delay, 'arguments': args})();
            if (fn.once) {
                onces.push(fn);
            }
		}, self);

        for (var i = 0, n = onces.length; i < n; i++) {
            A.erase(self[EVENT_CACHE][type], onces[i]);
        }
        onces = null;
		
        return self;
	};

	Events[METHOD_REMOVE] = function(type, fn) {
        var self = this;

		type = self[METHOD_REMOVEON](type);
		
        if (!self[EVENT_CACHE][type]) {
            return self;
        }

		if (!fn.internal) {
            A.erase(self[EVENT_CACHE][type], fn);
        }

		return self;
	};

	Events[METHOD_REMOVES] = function(events) {
        var self = this;

		if (Z.isPlainObject(events)) {
			for (var type in events) self[METHOD_REMOVE](type, events[type]);
			return self;
		}

		if (events) {
            events = self[METHOD_REMOVEON](events);
        }

		for (var type in self[EVENT_CACHE]) {
			if (events && events != type) {
                continue;
            }

			var fns = self[EVENT_CACHE][type];
			for (var i = fns.length; i--; i) {
                self[METHOD_REMOVE](type, fns[i]);
            }
		}

		return self;
	};

	Events[METHOD_REMOVEON] = function(string){
		return string.replace(/^on([A-Z])/, function(full, first) {
			return first.toLowerCase();
		});
	};

    Z.Events = Events;

});